using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using Random = UnityEngine.Random;

namespace Learning
{
    public class GPUDrawProcedural : MonoBehaviour
    {
        private static readonly Vector4[] VERTICES = {
            new Vector4(-0.5f, 0, 0f, 1),
            new Vector4(-0.5f, 0, 1f, 1),
            new Vector4(0.5f, 0, 1f, 1),
            new Vector4(0.5f, 0, 0f, 1),
        };
        
        private static readonly float[] INDICES = {
            0, 1, 2, 3, 0, 2
        };
        
        private static readonly Vector4[] UVS = {
            new Vector4(0, 0, 0, 0),
            new Vector4(1, 0, 0, 0),
            new Vector4(1, 1, 0, 0),
            new Vector4(0, 1, 0, 0),
        };
        
        /// <summary>
        /// GPU Instancing 一个批次可提交的数量
        /// 注意：这里和之前的不一致，之前传递给Shader的是Buffer，而这里的Shader接收的是数组，所以这个值要和Shader的数组长度对应
        /// </summary>
        private const int BATCH = 128;
        
        private const float Range = 100000f;
        private readonly Bounds BOUNDS = new Bounds(Vector3.zero, new Vector3(Range, Range, Range));
        
        public int instanceCount = 200;
        public Material instanceMaterial;
        
        private ArmyLineInfo[] _lineInfos; // 每条线段的信息
        
        void Start()
        {
            instanceMaterial.SetVectorArray("lineVertices", VERTICES);
            instanceMaterial.SetFloatArray("lineIndex", INDICES);
            instanceMaterial.SetVectorArray("lineUV", UVS);
            
            float3 center = float3.zero;
            float angle = 360f / instanceCount;
            float radius = 100f;
            
            _lineInfos = new ArmyLineInfo[instanceCount];
            for (int i = 0; i < instanceCount; i++)
            {
                float radian = angle * i * Mathf.Deg2Rad;
                
                var x = center.x + math.cos(radian) * radius;
                var z = center.z + math.sin(radian) * radius;
                
                var info = CreateLine(new float3(x, 0, z), center, Random.Range(3f, 5f), (i % 2 == 0) ? new float4(1, 0, 0, 1) : new float4(0, 1, 0, 1));
                _lineInfos[i] = info;
            }
        }

        void Update()
        {
            var batchCount = (int) math.ceil((float) instanceCount / (float) BATCH);
            for (int i = 0; i < batchCount; i++)
            {
                var startIndex = i * BATCH;
                var len = BATCH;
                if (i == batchCount - 1)
                {
                    len = instanceCount - (BATCH * i);
                }

                // 这里应该做成对象池，这里图方便就暴力创建了
                var batchInfo = new BatchInfo();
                
                // 填充数据，数据长度固定才能使用对象池
                var transArr = new Matrix4x4[BATCH];
                var colorArr = new Vector4[BATCH];
                for (int j = 0; j < len; j++)
                {
                    var info = _lineInfos[startIndex + j];
                    transArr[j] = info.mat;
                    colorArr[j] = info.color;
                }
                batchInfo.block = new MaterialPropertyBlock();
                batchInfo.block.SetMatrixArray("trsArr", transArr);
                batchInfo.block.SetVectorArray("colorArr", colorArr);

                var args = new uint[5] {0, 0, 0, 0, 0};
                args[0] = (uint) INDICES.Length; // 每个实例的索引数
                args[1] = (uint) len; // 实例数
                args[2] = 0; // 起始索引位置
                args[3] = 0; // 基顶点位置
                args[4] = 0; // 起始实例位置
                batchInfo.argsBuf = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 1, 5 * sizeof(uint));
                batchInfo.argsBuf.SetData(args);

                // 绘制
                Graphics.DrawProceduralIndirect(instanceMaterial, BOUNDS, MeshTopology.Triangles, batchInfo.argsBuf,0,
                    null, batchInfo.block, ShadowCastingMode.Off, false, LayerMask.NameToLayer("Character"));
            }
        }

        private ArmyLineInfo CreateLine(float3 origin, float3 target, float width, float4 color)
        {
            var vector = target - origin;
            var length = math.length(vector);
            if (length <= 0.01f)
            {
                return null;
            }
            var quat = quaternion.LookRotation(vector, new float3(0, 1, 0));
            var scale = new float3(width, 1, length);
            var mat = float4x4.TRS(origin, quat, scale);
            
            var info = new ArmyLineInfo();
            info.mat = mat;
            info.color = color;
            return info;
        }
    }

    public class ArmyLineInfo
    {
        public float4x4 mat;
        public float4 color;
    }
}
